不知不覺已經來到了最後的兩天,在Spring Security的部分除了設置權限、指定帳密之外,我們也可以從資料庫中取得帳號密碼,以及對應的權限,而且要增加保密的程度,JWT的方式也是一種強化。但因為時間有限,這些部份未來有機會再跟大家分享。
我們的目的是能夠設置一個網站,所以Security設定好之後,我們也需要去前端去新增登入的方法,才能完整的完成前後端整合
所以今天,是要來教大家從後端開發登入的API,最後一天,則是教大家從前端去呼叫這個登入的API
那就讓我們開始吧!
我們要提供API,就要告訴前端我們要輸入的資料型態,所以我們要先創建登入的資料型態,在dto的資料夾中,新增一個LoginDto.java這個資料夾
裡面的程式碼如下,重點就是提供String型態的username跟Password。
package net.Eric.accounting.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class LoginDto {
private String username;
private String password;
public LoginDto() {
}
public LoginDto(String username,String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
在Service中先創建AuthService.java
程式碼如下,我們這次只要做登入方法即可
package net.Eric.accounting.service;
import net.Eric.accounting.dto.LoginDto;
public interface AuthService {
String login(LoginDto loginDto);
}
這個方法是取得Config中的帳號密碼設定,然後跟登入的帳號密碼做比對,如果匹配成功,就去提供對應的權限。
package net.Eric.accounting.service.Impl;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import net.Eric.accounting.dto.LoginDto;
import net.Eric.accounting.service.AuthService;
@Service
@Component
public class AuthServiceImpl implements AuthService{
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public String login(LoginDto loginDto) {
try {
// 從 UserDetailsService 中獲取用戶資訊
UserDetails userDetails = userDetailsService.loadUserByUsername(loginDto.getUsername());
// 檢查密碼是否匹配
if (passwordEncoder.matches(loginDto.getPassword(), userDetails.getPassword())) {
// 如果驗證成功,可以在這裡返回成功的訊息
return "Login successful!";
} else {
// 密碼不匹配
return "Invalid username or password!";
}
} catch (Exception e) {
// 如果驗證失敗,返回錯誤訊息
return "Invalid username or password!";
}
}
}
完成之後我們回到Controller層,創建一個AuthController
程式碼會如下,只要接收帳密,並且呼叫登入的功能即可
package net.Eric.accounting.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import net.Eric.accounting.dto.LoginDto;
import net.Eric.accounting.service.AuthService;
@CrossOrigin("*")
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthService authService;
//Build Login RESt API
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginDto loginDto){
String response = authService.login(loginDto);
return new ResponseEntity<>(response,HttpStatus.CREATED);
}
}
在這裡,我們就已經創建了這個API。大家可知道所有的API都會被Security限制,而登入的API我們是開放給任何腳色使用,只有輸入正確帳密的人才會給予對應權限,所以login這個API是要permitAll的
程式碼如下
package net.Eric.accounting.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import lombok.AllArgsConstructor;
@Configuration
@AllArgsConstructor
public class SpringSecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf((csrf) -> csrf.disable())
.authorizeHttpRequests((authorize)->{
authorize.requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll();
authorize.requestMatchers(HttpMethod.POST,"/api/**").hasRole("ADMIN");
authorize.requestMatchers(HttpMethod.GET,"/api/**").hasAnyRole("ADMIN","USER");
authorize.anyRequest().authenticated();
}).httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails Eric = User.builder()
.username("Eric")
.password(passwordEncoder().encode("123321"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(Eric,admin);
}
}
修改完之後,我們可以在用POSTMAN測試,只要回傳200K即成功了!